package com.sakura.oj.judge.impl;

import cn.hutool.json.JSONUtil;
import com.sakura.oj.common.ErrorCode;
import com.sakura.oj.exception.BusinessException;
import com.sakura.oj.judge.JudgeService;
import com.sakura.oj.judge.codesandbox.CodeSandbox;
import com.sakura.oj.judge.codesandbox.CodeSandboxFactory;
import com.sakura.oj.judge.codesandbox.CodeSandboxProxy;
import com.sakura.oj.judge.codesandbox.model.ExecuteCodeRequest;
import com.sakura.oj.judge.codesandbox.model.ExecuteCodeResponse;
import com.sakura.oj.judge.codesandbox.model.JudgeInfo;
import com.sakura.oj.judge.model.JudgeResponse;
import com.sakura.oj.model.entity.JudgeConfig;
import com.sakura.oj.model.entity.Problem;
import com.sakura.oj.model.entity.ProblemSubmit;
import com.sakura.oj.model.enums.CodeSandBoxStatusEnums;
import com.sakura.oj.model.enums.JudgeInfoMessageEnum;
import com.sakura.oj.model.enums.ProblemStatusEnum;
import com.sakura.oj.model.enums.ProblemSubmitStatusEnum;
import com.sakura.oj.service.ProblemJudgeCaseService;
import com.sakura.oj.service.ProblemService;
import com.sakura.oj.service.ProblemSubmitService;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * 远程判题服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RemoteJudgeService implements JudgeService {

    private final ProblemService problemService;
    private final ProblemSubmitService submitService;
    private final ProblemJudgeCaseService judgeCaseService;

    @Setter
    @Value("${codesandbox.type:example}")
    private String type;

    @Setter
    @Value("${codesandbox.url}")
    private String judgeUrl;


    @Override
    public List<JudgeResponse> judge(Long submitId) {
        // 1) 根据传入的提交id查询 对应的题目提交信息
        ProblemSubmit problemSubmit = submitService.getById(submitId);

        // 2）判断提交信息是否在等待中，如果是就继续判题，如果不是就直接返回
        if (problemSubmit == null) {
            throw new RuntimeException("提交信息不存在");
        }

        if (!ProblemSubmitStatusEnum.WAITING.getValue().equals(problemSubmit.getStatus())) {
            throw new RuntimeException("题目已经在判题或已经判题完成");
        }
        Long problemId = problemSubmit.getProblemId();

        Problem problem = problemService.getById(problemId);
        if (problem == null || !ProblemStatusEnum.PUBLISHED.getValue().equals(problem.getStatus())) {
            throw new RuntimeException("题目不存在或题目未发布");
        }
        String judgeConfigStr = problem.getJudgeConfig();
        JudgeConfig judgeConfig = JSONUtil.toBean(judgeConfigStr, JudgeConfig.class);

        String code = problemSubmit.getCode();
        String language = problemSubmit.getLanguage();
        // 获取输入列表
        HashMap<String, List<String>> caseList = judgeCaseService.getInputOutputList(problemId);
        List<String> inputList = caseList.get("input");
        List<String> outputList = caseList.get("output");

        // 3）更改题目提交状态为 判题中，防止重复执行，也让用户看到实时状态
        ProblemSubmit updateProblemSubmit = new ProblemSubmit();
        updateProblemSubmit.setId(submitId);
        updateProblemSubmit.setStatus(ProblemSubmitStatusEnum.RUNNING.getValue());
        submitService.updateById(updateProblemSubmit);

        // 4）调用沙箱服务，执行代码，得到执行结果
        CodeSandbox codeSandbox = CodeSandboxFactory.newInstance(type);
        CodeSandboxProxy codeSandboxProxy = new CodeSandboxProxy(codeSandbox);
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest(code, inputList, language, judgeUrl);
        // 返回inputList的结果
        ExecuteCodeResponse executeCodeResponse = codeSandboxProxy.executeCode(executeCodeRequest);
        if (executeCodeResponse == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "沙箱执行异常");
        }

        // 处理编译错误、运行错误等情况
        // 根据新的API，status=1表示成功，3表示编译错误，4表示运行错误
        if (!Objects.equals(executeCodeResponse.getStatus(), CodeSandBoxStatusEnums.ACCEPTED.getValue())) {
            updateProblemSubmit.setStatus(ProblemSubmitStatusEnum.FAILED.getValue());

            // 构造扩展的judgeInfo，包含错误信息
            HashMap<String, Object> errorInfo = new HashMap<>();
            errorInfo.put("message", executeCodeResponse.getMessage());

            // 设置错误类型
            String errorType = switch (executeCodeResponse.getStatus()) {
                case 3 -> CodeSandBoxStatusEnums.COMPILE_FAILED.getText();
                case 4 -> CodeSandBoxStatusEnums.RUNTIME_ERROR.getText();
                case 5 -> CodeSandBoxStatusEnums.DANGEROUS_CODE.getText();
                default -> CodeSandBoxStatusEnums.FAILED.getText();
            };
            errorInfo.put("errorType", errorType);

            // 设置为JSON字符串
            updateProblemSubmit.setJudgeInfo(JSONUtil.toJsonStr(errorInfo));
            submitService.updateById(updateProblemSubmit);

            // 直接返回空结果，不再继续判题
            return List.of();
        }

        // 5）根据沙箱返回的结果，更新题目状态，并返回对应的信息
        List<String> executeCodeResponseOutputList = executeCodeResponse.getOutputList();
        // 判断输出结果数量是否与输入结果的数量是否匹配
        if (outputList == null || outputList.size() != inputList.size() ||
                executeCodeResponseOutputList == null || executeCodeResponseOutputList.size() != inputList.size()) {
            updateProblemSubmit.setStatus(ProblemSubmitStatusEnum.FAILED.getValue());

            // 构造错误信息
            HashMap<String, Object> errorInfo = new HashMap<>();
            errorInfo.put("message", "输出结果与预期不符合");
            errorInfo.put("errorType", "运行错误");

            updateProblemSubmit.setJudgeInfo(JSONUtil.toJsonStr(errorInfo));
            submitService.updateById(updateProblemSubmit);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "输出结果与预期不符合");
        }

        // 每个用例的运行信息
        List<JudgeInfo> runInfoList = executeCodeResponse.getRunInfoList();
        List<JudgeResponse> judgeResponses = new ArrayList<>(outputList.size() + 5);
        Long time_limit = judgeConfig.getTime_limit();
        Long memory_limit = judgeConfig.getMemory_limit();
        int executeCodeSize = executeCodeResponseOutputList.size();
        int countFlag = 0;

        // 创建一个列表存储失败用例的信息
        List<HashMap<String, Object>> failedCases = new ArrayList<>(10);

        for (int i = 0; i < executeCodeSize; i++) {
            String expectedOutput = outputList.get(i);
            String executeOutput = executeCodeResponseOutputList.get(i);
            String input = inputList.get(i);
            JudgeInfo runInfo = runInfoList.get(i);

            Long runTime = runInfo.getTime();
            Long runMemory = runInfo.getMemory();

            JudgeResponse judgeResponse;
            boolean casePassed = true;
            String failReason = null;

            // 修复时间和内存限制的判断 (之前弄反了)
            if (runMemory > memory_limit) {
                judgeResponse = JudgeResponse.builder()
                        .status(JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED.getValue())
                        .message(JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED.getText())
                        .build();
                casePassed = false;
                failReason = JudgeInfoMessageEnum.MEMORY_LIMIT_EXCEEDED.getText();
            } else if (runTime > time_limit) {
                judgeResponse = JudgeResponse.builder()
                        .status(JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED.getValue())
                        .message(JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED.getText())
                        .build();
                casePassed = false;
                failReason = JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED.getText();
            } else if (!expectedOutput.equals(executeOutput)) {
                judgeResponse = JudgeResponse.builder()
                        .status(JudgeInfoMessageEnum.WRONG_ANSWER.getValue())
                        .message(JudgeInfoMessageEnum.WRONG_ANSWER.getText())
                        .build();
                casePassed = false;
                failReason = JudgeInfoMessageEnum.WRONG_ANSWER.getText();
            } else {
                judgeResponse = JudgeResponse.builder()
                        .status(JudgeInfoMessageEnum.ACCEPTED.getValue())
                        .message(JudgeInfoMessageEnum.ACCEPTED.getText())
                        .build();
                countFlag++;
            }

            // 如果用例未通过，添加到失败用例列表
            if (!casePassed) {
                HashMap<String, Object> failedCase = new HashMap<>();
                failedCase.put("caseIndex", i);
                failedCase.put("input", input);
                failedCase.put("expectedOutput", expectedOutput);
                failedCase.put("actualOutput", executeOutput);
                failedCase.put("reason", failReason);
                failedCase.put("time", runTime);
                failedCase.put("memory", runMemory);
                failedCases.add(failedCase);
            }

            judgeResponses.add(judgeResponse);
        }

        Problem updateProblem = new Problem();
        updateProblem.setId(problemId);

        // 如果所有用例都通过，则题目提交通过
        if (countFlag == executeCodeSize) {
            updateProblemSubmit.setStatus(ProblemSubmitStatusEnum.ACCEPTED.getValue());

            // 全部通过，构造成功信息
            HashMap<String, Object> successInfo = new HashMap<>();
            // 使用第一个用例的执行信息作为整体信息
            JudgeInfo firstRunInfo = executeCodeResponse.getRunInfoList().get(0);
            successInfo.put("time", firstRunInfo.getTime());
            successInfo.put("memory", firstRunInfo.getMemory());
            successInfo.put("message", JudgeInfoMessageEnum.ACCEPTED.getText());

            updateProblemSubmit.setJudgeInfo(JSONUtil.toJsonStr(successInfo));
            updateProblem.setAcceptedNum(problem.getAcceptedNum() + 1);
        } else {
            updateProblemSubmit.setStatus(ProblemSubmitStatusEnum.FAILED.getValue());

            // 构造包含失败用例的判题信息
            HashMap<String, Object> extendedJudgeInfo = new HashMap<>();

            // 使用第一个运行信息的时间和内存数据
            JudgeInfo firstRunInfo = executeCodeResponse.getRunInfoList().get(0);
            extendedJudgeInfo.put("time", firstRunInfo.getTime());
            extendedJudgeInfo.put("memory", firstRunInfo.getMemory());

            // 获取失败的第一个用例的错误信息作为整体错误信息
            String failMessage = failedCases.get(0).get("reason").toString();
            extendedJudgeInfo.put("message", failMessage);
            extendedJudgeInfo.put("failedCases", failedCases);

            updateProblemSubmit.setJudgeInfo(JSONUtil.toJsonStr(extendedJudgeInfo));
        }
        submitService.updateById(updateProblemSubmit);
        // 修改题目通过数
        problemService.updateById(updateProblem);
        return judgeResponses;
    }
}
